#!/usr/bin/env python3
# _*_coding:utf-8 _*_
__author__ = "Gao"
from django.conf import settings

import os
import shutil
from urllib import parse
import subprocess
import stat
import paramiko
import re
from socket import timeout
from paramiko.ssh_exception import NoValidConnectionsError
from paramiko.ssh_exception import AuthenticationException
from paramiko.buffered_pipe import PipeTimeout
from paramiko.ssh_exception import SSHException

from repoistory import models


def build(project_obj,branch,**kwargs):
    '''执行打包操作'''
    git_clone(project_obj,branch)    # 执行git_clone操作

    replace_config(project_obj)    # 替换配置文件

    build_project(project_obj)    # 打包


def backup(selected_ids,**kwargs):
    '''连接服务器，执行备份操作'''
    selected_ids = selected_ids.split(',')
    submoduls = models.SubModul.objects.filter(id__in=selected_ids).prefetch_related('submodul2host_set').order_by('order')
    for submodul in submoduls:
        hosts = submodul.submodul2host_set.prefetch_related('host').all()
        for host in hosts:
            hostname = host.host.ip_address
            port = host.host.ssh_port
            username = host.host.ssh_user
            password = host.host.ssh_password
            if not host.backup_command:
                err_msg = '%s主机未配置备份命令'%hostname
                raise Exception(err_msg)
            command_list = [(host.backup_command, '备份')]
            _do_ssh_command(hostname, port, username, password, command_list)


def restart_service(selected_ids,**kwargs):
    '''连接服务器，执行重启服务操作'''
    selected_ids = selected_ids.split(',')
    submoduls = models.SubModul.objects.filter(id__in=selected_ids).prefetch_related('submodul2host_set').order_by(
        'order')
    for submodul in submoduls:
        hosts = submodul.submodul2host_set.prefetch_related('host').all()
        for host in hosts:
            hostname = host.host.ip_address
            port = host.host.ssh_port
            username = host.host.ssh_user
            password = host.host.ssh_password
            if not host.stop_command:
                err_msg = '%s主机未配置停止服务命令'%hostname
                raise Exception(err_msg)
            if not host.start_command:
                err_msg = '%s主机未配置启动服务命令'%hostname
                raise Exception(err_msg)
            command_list = []
            command_list.append((host.stop_command,'停止服务'))
            command_list.append((host.start_command, '启动服务'))
            _do_ssh_command(hostname, port, username, password, command_list)


def update(project_obj,selected_ids,**kwargs):
    '''连接服务器，执行更新操作'''
    selected_ids = selected_ids.split(',')
    submoduls = models.SubModul.objects.filter(id__in=selected_ids).prefetch_related('submodul2host_set')\
        .order_by('order')
    for submodul in submoduls:
        hosts = submodul.submodul2host_set.prefetch_related('host').all()
        for host in hosts:
            hostname = host.host.ip_address
            port = host.host.ssh_port
            username = host.host.ssh_user
            password = host.host.ssh_password
            source_file = host.source_file.replace('$WORPSPACE',project_obj.workspace)\
                .replace('$REPOSITORY',project_obj.repo_dir)
            target_file = host.target_file
            source_dir = os.path.dirname(os.path.abspath(source_file))
            source_tmp = os.path.join(source_dir,'tmp.tar')    # 临时压缩文件
            filename = os.path.basename(source_file)    # 项目文件名
            target_tmp = os.path.join(os.sep,'tmp','version_control','tmp.tar')
            if filename == '*':    # 更新的是整个目录
                if not os.listdir(source_dir):
                    raise Exception('%s不存在' % source_file)
            elif not os.path.exists(source_file):
                raise Exception('%s不存在'%source_file)
            subprocess.run('cd {source_dir};tar -czf tmp.tar {filename}'
                           .format(source_dir=source_dir,filename=filename),shell=True)    # 传送前先进行压缩
            command_list = []    # 依次要执行的命令
            _do_ssh_command(hostname, port, username, password, [('mkdir -p /tmp/version_control', '创建临时目录')])
            _do_sftp(hostname,port,username,password,source_tmp,target_tmp)

            command_list.append(('cd /tmp/version_control/;tar -xf tmp.tar','解压项目文件'))
            command_list.append(('rm -f /tmp/version_control/tmp.tar', '删除压缩文件'))
            if host.stop_command:
                command_list.append((host.stop_command, '停止服务'))
            if host.clean_old_file_command:
                command_list.append((host.clean_old_file_command, '清理旧文件'))
            command_list.append(('cp -rf /tmp/version_control/%s %s'%(filename,target_file),'替换文件'))
            if host.start_command:
                command_list.append((host.start_command, '启动服务'))
            command_list.append(('rm -rf /tmp/version_control/*', '清理临时目录'))
            _do_ssh_command(hostname, port, username, password, command_list)

            subprocess.run('rm -rf {source_tmp}'.format(source_tmp=source_tmp),shell=True)    # 清理压缩文件


def build_update(project_obj,branch,selected_ids,**kwargs):
    '''连接服务器，执行打包并更新操作'''
    build(project_obj,branch)
    update(project_obj,selected_ids)


def git_clone(project_obj,branch):
    '''git_clone'''
    if not os.path.exists(project_obj.repo_dir):
        os.makedirs(project_obj.repo_dir)
    while True:
        # 清空目录
        try:
            for f in os.listdir(project_obj.repo_dir):
                shutil.rmtree(os.path.join(project_obj.repo_dir,f))
        except Exception as e:
            error_path = e.__str__().split(' ')[-1].strip("'")    # windows下解决没有权限的问题
            os.chmod(error_path, stat.S_IWRITE)
            continue
        else:
            break
    git_address = parse.urlsplit(project_obj.git_address)
    schema = git_address.scheme
    netloc = git_address.netloc
    path = git_address.path
    name = project_obj.auth.name
    password = project_obj.auth.password
    git_url = '{schema}://{name}:{password}@{netloc}{path}'.format(schema=schema, name=name, password=password,
                                                                   netloc=netloc, path=path)
    clone_cmd = 'git clone -b {branch} {git_url}'.format(branch=branch, git_url=git_url)
    clone_res = subprocess.Popen(clone_cmd, cwd=project_obj.repo_dir, shell=True)  # 切换到repo目录执行
    if clone_res.wait() != 0:
        raise Exception('Git clone失败，请检查Git地址及账号')


def replace_config(project_obj):
    '''替换配置文件'''
    for submodul in project_obj.submodul_set.all():
        for configuration in submodul.configuration_set.all():
            file_path = configuration.file_path.replace('$WORKSPACE',project_obj.workspace)\
                .replace('$REPOSITORY',project_obj.repo_dir)
            with open(file_path,'w',encoding='utf-8') as f:
                f.write(configuration.config)


def build_project(project_obj):
    '''打包项目'''
    set_path = 'WORKSPACE=%s;REPOSITORY=%s;' % (project_obj.workspace, project_obj.repo_dir)  # 设置环境变量
    if not project_obj.build_command:
        err_msg = '%s项目未配置打包命令' % project_obj.name
        raise Exception(err_msg)
    build_command = set_path + project_obj.build_command.replace('\r', ';')  # 拼接打包命令
    # build_res = subprocess.Popen(build_command,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
    build_res = subprocess.getstatusoutput(build_command)
    if build_res[0] != 0:
        raise Exception(build_res[1])
    msg = build_res[1]
    if re.search('error', msg, re.IGNORECASE):
        raise Exception(msg)



def _do_ssh_command(hostname,port,username,password,command_list):
    '''
    使用paramiko执行命令

    Args:
        hostname: 远程主机地址
        port: ssh端口
        username: ssh用户名
        password: ssh密码
        command_list: 需要执行的一组命令，格式为: [(command,command_nickname)]

    Returns:
        stderr: 执行命令返回的错误消息
    '''
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy)
    try:
        ssh.connect(hostname=hostname, port=port, username=username, password=password, timeout=15)
        for command,command_nickname in command_list:
            try:
                stdin, stdout, stderr = ssh.exec_command(command, timeout=180)
            except PipeTimeout:
                err_msg = '%s主机上执行%s命令超时' % (hostname, command_nickname)
                raise Exception(err_msg)
            else:
                stderr_msg = stderr.readline()
                if stderr_msg:
                    err_msg = '%s主机上执行%s命令失败\n' % (hostname, command_nickname)
                    raise Exception(err_msg + stderr_msg)
    except (timeout, NoValidConnectionsError):
        err_msg = '%s:%s主机无法连接' % (hostname, port)
        raise Exception(err_msg)
    except AuthenticationException:
        err_msg = '%s主机的SSH用户名和密码有误' % (hostname)
        raise Exception(err_msg)
    finally:
        ssh.close()


def _do_sftp(hostname,port,username,password,source_file,target_file):
    '''
    使用paramiko传送文件

    Args:
        hostname: 远程主机地址
        port: ssh端口
        username: ssh用户名
        password: ssh密码
        source_file: 本地文件路径
        target_file: 远程文件路径
    '''
    transport = paramiko.Transport(hostname, port)
    try:
        transport.connect(username=username, password=password)
        sftp = paramiko.SFTPClient.from_transport(transport)
        sftp.put(source_file, target_file)
    except AuthenticationException:
        err_msg = '%s主机的SSH用户名和密码有误' % (hostname)
        raise Exception(err_msg)
    except SSHException:
        err_msg = '%s:%s主机无法连接' % (hostname, port)
        raise Exception(err_msg)
    finally:
        transport.close()
